#include "refAnalyser.h"
#include "common.h"
#include <cmath>
#include <new>

hashStats::hashStats() {
}

hashStats::hashStats(const hashStats& rhs) {
	this->LocLengthStat = vector<int>(rhs.LocLengthStat.size(), 0);
	for (int i = 0; i < rhs.LocLengthStat.size(); i++) {
		this->LocLengthStat[i] = rhs.LocLengthStat[i];
	}
	this->avgLocLength = rhs.avgLocLength;
	this->totalLocLength = rhs.totalLocLength;
}

hashStats::~hashStats() {
	this->LocLengthStat.clear();
}

void hashStats::printStats() {
	cout << "Total LocLength: " << totalLocLength << endl;
	cout << "Average location list length: " << avgLocLength << endl;

	cout << "Kmer categories: " << endl;
	for (int i = 0; i < LocLengthStat.size(); i++) {
		cout << "from " << pow(10.0, i) << " to " << pow(10.0, i + 1) - 1
				<< " : " << LocLengthStat[i] << endl;
	}
}

refAnalyser::refAnalyser() {
}

refAnalyser::refAnalyser(string refFileName, int KmerSize, int maxError) {
	chrHashData =
			map<KmerHash, int, bool (*)(const KmerHash&, const KmerHash&)>(
					&KmerHash::compare);
	this->refFileName = refFileName;
	this->maxError = maxError;
	this->KmerSize = KmerSize;
	conductedAF = vector<unsigned long int>(this->maxError + 1, 0);
	for (int e = 0; e <= maxError; e++)
		conductedAF[e] = 0;
	refFile.open(refFileName.c_str());
}

refAnalyser::refAnalyser(const refAnalyser& rhs) {
	chrHashData = rhs.chrHashData;
	refStat = hashStats(rhs.refStat);
	KmerSize = rhs.KmerSize;
	maxError = rhs.maxError;
	conductedAF = vector<unsigned long int>(rhs.conductedAF);
	refFileName = rhs.refFileName;
	refFile.open(refFileName.c_str());
}

refHash* refAnalyser::clone() {
	return new refAnalyser(*this);
}

refAnalyser& refAnalyser::operator=(const refAnalyser &rhs) {
	chrHashData = rhs.chrHashData;
	refStat = hashStats(rhs.refStat);
	KmerSize = rhs.KmerSize;
	maxError = rhs.maxError;
	conductedAF = vector<unsigned long int>(rhs.conductedAF);
	refFileName = rhs.refFileName;
	refFile.open(refFileName.c_str());

	return *this;
}

refAnalyser::~refAnalyser() {
	refFile.close();
}

void refAnalyser::loadHash(int chrNum) {

	int chrSize = countChr(chrNum);
	int progressCount = 0;

	cerr << "chrSize: " << chrSize << endl;

	chrHashData.clear();
	gotoChr(chrNum);

	char tempChar;
	string tempKey;

	int skipN = 0;

	//get first Kmer
	for (int i = 0; i < KmerSize; i++) {
		//show progress
		makeProgress("loadHash", progressCount, chrSize, 100000);

		refFile >> tempChar;
		if (tempChar == 'N' || tempChar == 'M' || tempChar == 'R') {
			skipN = KmerSize;
		}
		tempKey += tempChar;
	}

	while (refFile.good() && tempChar != '>') {
		//show progress
		makeProgress("loadHash", progressCount, chrSize, 100000);

		//If there is any N, we skip the Kmer
		if (tempChar == 'N' || tempChar == 'M' || tempChar == 'R') {
			skipN = KmerSize;
		}

		if (skipN == 0) {
			//Allocate if doesn't exist, otherwise, increment.
			if (chrHashData.find(KmerHash(tempKey, KmerSize))
					!= chrHashData.end())
				chrHashData[KmerHash(tempKey, KmerSize)]++;
			else
				chrHashData[KmerHash(tempKey, KmerSize)] = 1;
		} else {
			skipN--;
		}
		refFile >> tempChar;
		tempKey = tempKey.substr(1, KmerSize - 1) + tempChar;
	}

	cout << "Verifying 0 entry Kmers" << endl;
	cout << "Num of Kmer in loadHash(): " << chrHashData.size() << endl;
	for (HTIter = chrHashData.begin(); HTIter != chrHashData.end(); HTIter++) {
		if (HTIter->second == 0) {
			cout << "hashIter " << (HTIter->first).getKmer()
					<< " has 0 content!" << endl;
		}
	}
}

void refAnalyser::unloadHash() {
	chrHashData.clear();
}

vector<unsigned long long> refAnalyser::query(string read) {
	return query(read, conductedAF);
}

vector<unsigned long long> refAnalyser::query(string read,
		vector<unsigned long int>& conductedAF) {
	vector<unsigned long long> locList;

	int KmerNum = read.length() / KmerSize;
	int cumulatedAF = 0;

	keyStruct * sortInput;

	try {
		sortInput = new keyStruct[KmerNum];
	} catch (const bad_alloc& ba) {
		cerr << "Caught not enough memory" << endl;
		cerr << "KmerNum = " << KmerNum << endl;
		cerr << "KmerSize = " << KmerSize << endl;
		cerr << "read.length() = " << read.length() << endl;
		cerr.flush();
		exit(1);
	}

	//Getting the Kmers
	for (int i = 0; i < KmerNum; i++) {
		string KmerString = read.substr(i * KmerSize, KmerSize);
		if (chrHashData.find(KmerHash(KmerString, KmerSize))
				!= chrHashData.end())
			sortInput[i].key_entry_size = chrHashData[KmerHash(KmerString,
					KmerSize)];
		else
			sortInput[i].key_entry_size = 0;
	}
	//Sort them
	qsort(sortInput, KmerNum, sizeof(keyStruct), compareEntrySize);

	int eMax = (maxError < KmerNum)? maxError : KmerNum - 1;

	for (int e = 0; e <= eMax; e++) {
		cumulatedAF += sortInput[e].key_entry_size;
		conductedAF[e] += cumulatedAF;
	}

	delete[] sortInput;

	locList.assign(cumulatedAF, 0);
	return locList;
}

void refAnalyser::genStat() {
	//Resetting the stats
	refStat.totalLocLength = 0;
	refStat.LocLengthStat.clear();

	cout << "Num of Kmer in genStat(): " << chrHashData.size() << endl;

	int progress = 0;
	cerr << "genStat" << endl;
	for (HTIter = chrHashData.begin(); HTIter != chrHashData.end(); HTIter++) {

		makeProgress("Gathering stats ", progress, (int) chrHashData.size(),
				100);

		if (HTIter->second == 0) {
			string Kmer = (HTIter->first).getKmer();
			cout << "hashIter " << Kmer << " has 0 content!" << endl;
			cout << "hashIter->second: " << HTIter->second << endl;
			cout << chrHashData[KmerHash(Kmer, KmerSize)] << endl;
			exit(1);
		}

		int logLv = (int) log10(HTIter->second);

		//		test if we should expand
		while (refStat.LocLengthStat.size() <= logLv) {
			refStat.LocLengthStat.push_back(0);
		}
		refStat.LocLengthStat[logLv]++;
		refStat.totalLocLength += HTIter->second;
	}
	refStat.avgLocLength = refStat.totalLocLength / (double) chrHashData.size();
}

void refAnalyser::printStat() {
	genStat();

	cout << "Num of Kmers: " << chrHashData.size() << endl;

	refStat.printStats();

	for (int e = 0; e <= maxError; e++)
		cout << "For e = " << e << ", conducted AF after CKS: "
				<< conductedAF[e] << endl;
}

void refAnalyser::gotoChr(int chrNum) {
	if (!refFile.good())
		refFile.clear();
	refFile.seekg(0, ios_base::beg);
	int chrCount = 0;
	string temp_string;
	//get to the chromosome
	while (1) {
		getline(refFile, temp_string);
		if (temp_string[0] == '>') {
			chrCount++;
			cout << temp_string << endl;
			if (chrCount == chrNum)
				return;
		}
	}
}

unsigned long int refAnalyser::countChr(int chrNum) {
	unsigned long int chrSize = 0;
	char tempChar = 'N';

	gotoChr(chrNum);

	while (refFile.good() && tempChar != '>') {
		refFile >> tempChar;
		chrSize++;
	}

	return chrSize;
}

void refAnalyser::begin() {
	HTIter = chrHashData.begin();
}

bool refAnalyser::end() {
	if (HTIter == chrHashData.end())
		return true;
	else
		return false;
}

void refAnalyser::next() {
	HTIter++;
}

string refAnalyser::getKmerString() {
	return HTIter->first.getKmer();
}

void refAnalyser::pruneKmer(string KmerString) {
	HTIter = chrHashData.find(KmerHash(KmerString, KmerSize));
	if (HTIter != chrHashData.end())
		chrHashData.erase(HTIter);
}
